﻿using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BReusable;
using System.Diagnostics;
namespace BReusable
{
	public abstract class BufferedLinqEntity<TContext, TEntity> : System.ComponentModel.IEditableObject
		where TContext : System.Data.Linq.DataContext
	{
		public delegate void NewEntityDelegate(BufferedLinqEntity<TContext,TEntity> item);
		public event NewEntityDelegate NewEntitySaved;


		TContext _dataContext;
		protected readonly TEntity _entity;
		private Action<TEntity> _attachAction;

		/// <summary>
		/// For use with already attached entities
		/// </summary>
		/// <param name="dataContext"></param>
		/// <param name="entity"></param>
		protected BufferedLinqEntity(TContext dataContext, TEntity entity)
		{
			_dataContext = dataContext;
			_entity = entity;
			IsAttached = BufferedLinqEntity<TContext, TEntity>.Attached.Attached;
		}
		/// <summary>
		/// For use with a new entity that has not been added to the InsertOnSubmit List
		/// </summary>
		/// <param name="dataContext"></param>
		/// <param name="entity"></param>
		/// <param name="AttachAction"></param>
		protected BufferedLinqEntity(TContext dataContext, TEntity entity, Action<TEntity> AttachAction)
			: this(dataContext, entity)
		{
			IsAttached = BufferedLinqEntity<TContext,TEntity>.Attached.Detached;
			_attachAction = AttachAction;
		}

		public TEntity getEntity()
		{
			return _entity;
		}

		protected TFieldType GetBufferedProperty<TFieldType>(String key, TFieldType bufferValue, TFieldType entityValue)
		{
			return Changes.ContainsKey(key) ? bufferValue : entityValue;
		}



		protected void SetBufferedProperty(string key, Action linqAction
			, bool linqEqualsValue, Action bufferAction)
		{
			if (linqEqualsValue)
			{
				if (Changes.ContainsKey(key))
					Changes.Remove(key);
			}
			else
				Changes.InsertOrUpdate(key, linqAction); bufferAction();
		}

		private Dictionary<String, Action> Changes = new Dictionary<string, Action>();

		public int ChangeCount { get { return Changes != null ? Changes.Count : 0; } }
		public bool hasChanges { get { return Changes != null ? Changes.Count > 0 : false; } }

		

		/// <summary>
		/// Learned about this from http://www.vbforums.com/showthread.php?t=584096
		/// Other sources:
		/// Is IEditableObject too hard?
		/// http://go.internet.com/?id=474X1146&url=http%3A%2F%2Fwww.madprops.org%2Fblog%2Fis-ieditableobject-too-hard%2F
		/// </summary>
		#region IEditableObject Members

		public void BeginEdit()
		{

		}

		public void CancelEdit()
		{
			if (Changes != null)
				Changes.Clear();
		}

		public enum Attached
		{
			Detached,ReadyToAttach,InSubmitQueue,Attached
		}
		public Attached IsAttached { get; private set; }
		
		public void Attach()
		{
			if(IsAttached== BufferedLinqEntity<TContext,TEntity>.Attached.Detached)
				IsAttached = BufferedLinqEntity<TContext, TEntity>.Attached.ReadyToAttach;
			
		}
		/// <summary>
		/// If item is ReadyToAttach then
		/// NewEntitySaved is raised before OnNewEntitySaved() is called
		/// </summary>
		public void EndEdit()
		{
			_dataContext.SubmitChanges();
			if (IsAttached == BufferedLinqEntity<TContext, TEntity>.Attached.ReadyToAttach)
			{
				_attachAction(_entity);
				IsAttached = BufferedLinqEntity<TContext, TEntity>.Attached.InSubmitQueue;
			}
			if (ChangeCount > 0)
			{
				Changes.ForEach((item) => item.Value.Invoke());
			}
			_dataContext.SubmitChanges();
			if (IsAttached == BufferedLinqEntity<TContext, TEntity>.Attached.InSubmitQueue)
			{
				IsAttached = BufferedLinqEntity<TContext, TEntity>.Attached.Attached;

				if (NewEntitySaved != null)

					NewEntitySaved(this);
				OnNewEntitySaved();
			}
			
		}

		virtual protected void OnNewEntitySaved() { }

		#endregion

		/// <summary>
		/// Code to attach to a bindingnavigator, and ask the user to save changes or not.
		/// </summary>
		/// <param name="form">used to make a call to Form.Validate
		/// in case the navigation is clicked while a control has not validated yet</param>
		/// <param name="bn">the bindingNavigator that is tied to your item</param>
		/// <param name="hasErrorsFunc">a call to a function that validates the current data showing</param>
		/// <param name="SubmitChangesAction">usually a call to the actual linq datacontext to save changes</param>
		/// <param name="clearErrors">usually clears all error provider errors,
		/// called just before moving,
		/// if the move is a success </param>
		/// <param name="OnMoveSuccess">What to do if the move is called usually something like bn.bindingsource.movenext, etc..</param>
		public static void ConditionalMove(Form form, BindingNavigator bn
			, Func<bool> hasErrorsFunc, Action clearErrors, Action OnMoveSuccess)
		{
			ConditionalMove(form, bn, hasErrorsFunc, clearErrors, OnMoveSuccess, () => bn.BindingSource.EndEdit());
		}

		/// <summary>
		/// Code to attach to a bindingnavigator, and ask the user to save changes or not.
		/// </summary>
		/// <param name="form">used to make a call to Form.Validate
		/// in case the navigation is clicked while a control has not validated yet</param>
		/// <param name="bn">the bindingNavigator that is tied to your item</param>
		/// <param name="hasErrorsFunc">a call to a function that validates the current data showing
		/// will ignore parameter if it is null</param>
		/// <param name="clearErrors">sually clears all error provider errors,
		/// called just before moving,
		/// if the move is a success</param>
		/// <param name="OnMoveSuccess">What to do if the move is called usually something like bn.bindingsource.movenext, etc..</param>
		/// <param name="OnSaveRequest">usually a call to bn.current.directcast.endEdit()</param>
		public static void ConditionalMove(Form form, BindingNavigator bn
			, Func<bool> hasErrorsFunc, Action clearErrors, Action OnMoveSuccess, Action OnSaveRequest)
		{
			Debug.Assert(bn.BindingSource != null);
			Debug.Assert(bn.BindingSource.Current != null);
			form.Validate();

			Action _OnMoveSuccess = () =>
			{
				clearErrors();
				OnMoveSuccess();

				//    itemChanges = false; 

			};

			var current = bn.BindingSource.Current.DirectCast<BufferedLinqEntity<TContext, TEntity>>();

			bool _hasErrors = false;
			if (hasErrorsFunc != null)
				_hasErrors = hasErrorsFunc();
			else
				Debug.Print("hasErrorsFunc is null, ignoring");
			var _hasChanges =current!=null? current.hasChanges:false;
			String prompt;
			if (_hasErrors)
				prompt = "Cannot save because of errors,fix errors?";
			else
				prompt = "Save changes?";

			if (_hasErrors || _hasChanges) //|| itemChanges)

				switch (prompt.ToMessageBox("Unsaved changes"
						, MessageBoxButtons.YesNoCancel))
				{
					case DialogResult.Yes:
						if (_hasErrors == false)
						{
							var saveSuccess = false;
							if (current.IsAttached != BufferedLinqEntity<TContext, TEntity>.Attached.Attached)
								current.Attach();
							try
							{
								OnSaveRequest();
								saveSuccess = true;

							}

							catch (Exception exception)
							{
								exception.Message.ToMessageBox();
							}

							if (saveSuccess) _OnMoveSuccess();
						}
						break;

					case DialogResult.No:
						bn.BindingSource.CancelEdit();
						current.CancelEdit();

						_OnMoveSuccess();

						break;

					case DialogResult.Cancel:

					//does nothing user wishes to stay and work on the current record

					default:

						break;

				}

			else

				_OnMoveSuccess();

		}
	}

}
